/*
 * Copyright 2010 Pierre-Yves Ricau
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.excilys.capiq.server;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.logging.Logger;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;

import org.jasig.cas.client.util.AssertionHolder;
import org.jasig.cas.client.validation.Assertion;

import com.excilys.capiq.client.exception.AnswerNotFoundException;
import com.excilys.capiq.client.exception.AnsweringSelfQuestionException;
import com.excilys.capiq.client.exception.EmptyFieldException;
import com.excilys.capiq.client.exception.NotLoggedInException;
import com.excilys.capiq.client.exception.QuestionAlreadyAnsweredException;
import com.excilys.capiq.client.exception.QuestionNeedsTwoAnswersException;
import com.excilys.capiq.client.service.QuestionService;
import com.excilys.capiq.server.model.Answer;
import com.excilys.capiq.server.model.Question;
import com.excilys.capiq.shared.AnswerDTO;
import com.excilys.capiq.shared.QuestionDTO;
import com.excilys.capiq.shared.UserScoreDTO;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

public class QuestionServiceImpl extends RemoteServiceServlet implements QuestionService {

	private static final long	serialVersionUID	= 1L;

	private static final Logger	LOG					= Logger.getLogger(QuestionServiceImpl.class.getName());

	@Override
	public AnswerDTO answerQuestion(Long id, String answer, boolean goodQuestion) throws QuestionAlreadyAnsweredException,
			AnsweringSelfQuestionException, AnswerNotFoundException, NotLoggedInException {
		checkLoggedIn();

		PersistenceManager pm = PMF.getPersistenceManager();

		try {
			Question question = pm.getObjectById(Question.class, id);

			if (question.getAuthor().equals(getUser())) {
				throw new AnsweringSelfQuestionException();
			}

			Query q = pm.newQuery(Answer.class, "author == u");
			q.declareParameters("com.google.appengine.api.users.User u");

			List<Answer> userAnswers = (List<Answer>) q.execute(getUser());

			for (Answer userAnswer : userAnswers) {
				if (userAnswer.getQuestionId().equals(question.getId())) {
					throw new QuestionAlreadyAnsweredException();
				}
			}

			int answerIndex = question.getAnswers().indexOf(answer);

			if (answerIndex == -1) {
				throw new AnswerNotFoundException();
			}

			Answer answerEntity = new Answer();
			answerEntity.setAnswerIndex(answerIndex);
			answerEntity.setAuthor(getUser());
			answerEntity.setGoodAnswer(answerIndex == question.getGoodAnswerIndex());
			answerEntity.setQuestionId(question.getId());
			answerEntity.setGoodQuestion(goodQuestion);

			pm.makePersistent(answerEntity);

			AnswerDTO answerDto = new AnswerDTO();

			answerDto.setGoodAnswer(question.getAnswers().get(question.getGoodAnswerIndex()));
			answerDto.setExplanation(question.getExplanation());

			return answerDto;

		} finally {
			pm.close();
		}

	}

	@Override
	public void createQuestion(String question, String explanation, List<String> answers, int goodAnswer)
			throws QuestionNeedsTwoAnswersException, AnswerNotFoundException, EmptyFieldException, NotLoggedInException {
		checkLoggedIn();

		if (answers.size() < 2) {
			throw new QuestionNeedsTwoAnswersException();
		}

		if (goodAnswer >= answers.size()) {
			throw new AnswerNotFoundException();
		}

		question = question.trim();

		if (question == null || question.equals("")) {
			throw new EmptyFieldException();
		}

		List<String> trimedAnswers = new ArrayList<String>();

		for (String answer : answers) {
			String trimedAnswer = answer.trim();
			if (trimedAnswer == null || trimedAnswer.equals("")) {
				throw new EmptyFieldException();
			} else {
				trimedAnswers.add(trimedAnswer);
			}
		}

		Question questionEntity = new Question();
		questionEntity.setQuestion(question);
		questionEntity.setAnswers(trimedAnswers);
		questionEntity.setGoodAnswerIndex(goodAnswer);
		questionEntity.setAuthor(getUser());
		questionEntity.setExplanation(explanation);

		PersistenceManager pm = PMF.getPersistenceManager();
		try {
			pm.makePersistent(questionEntity);
		} finally {
			pm.close();
		}

	}

	@Override
	public QuestionDTO getQuestion() throws NotLoggedInException {
		checkLoggedIn();

		PersistenceManager pm = PMF.getPersistenceManager();

		/*
		 * TODO Clearly not an efficient way to find a new question
		 */
		try {
			Query q = pm.newQuery(Answer.class, "author == u");
			q.declareParameters("String u");

			List<Answer> answers = (List<Answer>) q.execute(getUser());

			q = pm.newQuery(Question.class, "author != u");
			q.declareParameters("String u");
			List<Question> questions = (List<Question>) q.execute(getUser());

			List<Long> answeredQuestionIds = new ArrayList<Long>();
			for (Answer answer : answers) {
				answeredQuestionIds.add(answer.getQuestionId());
			}

			List<Question> questionsNotAnswered = new ArrayList<Question>();

			for (Question question : questions) {
				if (!answeredQuestionIds.contains(question.getId())) {
					questionsNotAnswered.add(question);
				}
			}

			if (questionsNotAnswered.size() == 0) {
				return null;
			}

			int questionIndex = new Random().nextInt(questionsNotAnswered.size());

			return questionsNotAnswered.get(questionIndex).toQuestionDTO();

		} finally {
			pm.close();
		}
	}

	@Override
	public List<UserScoreDTO> getUserScores() throws NotLoggedInException {

		// Notice that we don't check that the user is logged in

		PersistenceManager pm = PMF.getPersistenceManager();

		/*
		 * TODO This code loads the whole DB, ouch!
		 */
		try {
			List<Question> questions = (List<Question>) pm.newQuery(Question.class).execute();

			List<Answer> answers = (List<Answer>) pm.newQuery(Answer.class).execute();

			Map<Long, List<Answer>> answersByQuestionId = new HashMap<Long, List<Answer>>();
			Map<String, List<Question>> usersQuestionsMap = new HashMap<String, List<Question>>();
			Map<String, List<Answer>> usersAnswersMap = new HashMap<String, List<Answer>>();
			List<String> users = new ArrayList<String>();

			for (Question question : questions) {
				String author = question.getAuthor();
				List<Question> userQuestions = usersQuestionsMap.get(author);
				if (userQuestions == null) {
					userQuestions = new ArrayList<Question>();
					usersQuestionsMap.put(author, userQuestions);
					if (!users.contains(author)) {
						users.add(author);
					}
				}
				userQuestions.add(question);
				answersByQuestionId.put(question.getId(), new ArrayList<Answer>());
			}

			for (Answer answer : answers) {
				String author = answer.getAuthor();
				List<Answer> userAnswers = usersAnswersMap.get(author);
				if (userAnswers == null) {
					userAnswers = new ArrayList<Answer>();
					usersAnswersMap.put(author, userAnswers);
					if (!users.contains(author)) {
						users.add(author);
					}
				}
				userAnswers.add(answer);
				answersByQuestionId.get(answer.getQuestionId()).add(answer);
			}

			List<UserScoreDTO> userScores = new ArrayList<UserScoreDTO>();

			for (String user : users) {
				UserScoreDTO userScore = new UserScoreDTO();
				userScore.setName(nicknameFromUserId(user));
				List<Question> userQuestions = usersQuestionsMap.get(user);

				int score = 0;
				if (userQuestions == null) {
					userScore.setQuestionCreated(0);
				} else {
					userScore.setQuestionCreated(userQuestions.size());
					for (Question userQuestion : userQuestions) {
						for (Answer questionAnswer : answersByQuestionId.get(userQuestion.getId())) {
							if (questionAnswer.isGoodQuestion()) {
								score += 1000;
							} else {
								score -= 1000;
							}
						}
					}
				}

				List<Answer> userAnswers = usersAnswersMap.get(user);
				if (userAnswers == null) {
					userScore.setQuestionAnswered(0);
					userScore.setGoodAnswers(0);
				} else {
					userScore.setQuestionAnswered(userAnswers.size());
					int goodAnswers = 0;
					for (Answer userAnswer : userAnswers) {
						if (userAnswer.isGoodAnswer()) {
							goodAnswers++;
						}
					}
					userScore.setGoodAnswers(goodAnswers);
				}

				score = score + 10000 * userScore.getGoodAnswers() + userScore.getQuestionAnswered() * 1000;

				userScore.setScore(score);

				userScores.add(userScore);
			}

			Collections.sort(userScores, new Comparator<UserScoreDTO>() {
				@Override
				public int compare(UserScoreDTO o1, UserScoreDTO o2) {
					return new Integer(o2.getScore()).compareTo(o1.getScore());
				}
			});

			return userScores;

		} finally {
			pm.close();
		}

	}

	private void checkLoggedIn() throws NotLoggedInException {
		if (getUser() == null) {
			throw new NotLoggedInException();
		}
	}

	private String getUser() {
		Assertion assertion = AssertionHolder.getAssertion();
		if (assertion == null) {
			return null;
		} else {
			return assertion.getPrincipal().getName();
		}
	}

	private String nicknameFromUserId(String userId) {
		return userId;
	}

}
